namespace FSharpChess
open System
open System.Threading
open System.Windows.Forms

module Funcs =
    type gametimes =
        {
            mutable ClockTime:TimeSpan;
            mutable ClockIncrementPerMove:TimeSpan;
            mutable ClockFixedTimePerMove:TimeSpan;
        }
    let gmtim =
        {
            ClockTime = new TimeSpan(0,5,0)
            ClockIncrementPerMove = new TimeSpan(0,0,0)
            ClockFixedTimePerMove = new TimeSpan(0,0,0)
        }
    //mutables
    let mutable EnablePondering = false
    let mutable MoveRedoList = new ResizeArray<Move> ()
    let mutable MoveAnalysis = new ResizeArray<Move> ()
    let mutable ShowThinking = false
    let mutable ShowOpenings = false
    let mutable DifficultyLevel = 10
    let mutable ClockMoves = 40
    let mutable Orientation = OriWhite
    let mutable wbActive = false
    let mutable PonderingHashCodeA = 0UL
    let mutable PonderingHashCodeB = 0UL
    let mutable PonderingStart = new DateTime()
    let GetClockTime() = Math.Max(Math.Round(gmtim.ClockTime.TotalMinutes, 0), 1.0)
    //Board
    let gm = Posn.start
    //functions
    let MoveNo(psn) = psn.Trn >>> 1
    let Flip() =
        Orientation <- if Orientation=OriWhite then OriBlack else OriWhite

    //WinBoard
    //form
    let lvwWinBoard1 = 
        let lvwWinBoard = new System.Windows.Forms.ListView()
        lvwWinBoard.Dock <- System.Windows.Forms.DockStyle.Fill
        lvwWinBoard.FullRowSelect <- true
        lvwWinBoard.HeaderStyle <- System.Windows.Forms.ColumnHeaderStyle.Nonclickable
        lvwWinBoard.Location <- new System.Drawing.Point(0, 0)
        lvwWinBoard.Name <- "lvwWinBoard"
        lvwWinBoard.Size <- new System.Drawing.Size(500, 250)
        lvwWinBoard.TabIndex <- 143
        lvwWinBoard.View <- System.Windows.Forms.View.Details
        let colDirection = new System.Windows.Forms.ColumnHeader()
        let colMessage = new System.Windows.Forms.ColumnHeader()
        let tmp=lvwWinBoard.Columns.Add(colDirection)
        let tmp=lvwWinBoard.Columns.Add(colMessage)
        colDirection.Text <- "Dir"
        colDirection.Width <- 29
        colMessage.Text <- "Message"
        colMessage.Width <- 400
        lvwWinBoard
    let frmWinBoard =
        let temp = new Form()
        temp.AutoScaleBaseSize <- new System.Drawing.Size(5, 13)
        temp.ClientSize <- new System.Drawing.Size(500, 250)
        temp.Controls.Add(lvwWinBoard1)
        temp.FormBorderStyle <- FormBorderStyle.Sizable
        temp.Name <- "frmWinBoard"
        temp.Text <- "WinBoard Message Log"
        temp.WindowState<-FormWindowState.Minimized
        temp
    let LogWinBoardMessageBase(strDirection, strMessage) =
        let lvi = [| strDirection; strMessage |]
        if lvwWinBoard1.Items.Count>50 then lvwWinBoard1.Items.Clear()
        lvwWinBoard1.Items.Add( new System.Windows.Forms.ListViewItem( lvi ) ) |> ignore
        lvwWinBoard1.EnsureVisible(lvwWinBoard1.Items.Count-1)
    let LogWinBoardMessage(strDirection, strMessage) =
        if frmWinBoard.InvokeRequired then
            frmWinBoard.Invoke(new MethodInvoker(fun() -> LogWinBoardMessageBase(strDirection, strMessage))) |> ignore
        else
            LogWinBoardMessageBase(strDirection, strMessage)
    let LogOut strMessage = LogWinBoardMessage("Out",strMessage)
    let wbSendOutputMessage(strMessage:string) =
        LogOut(strMessage)
        Console.WriteLine(strMessage)
    let wbSendDrawByFiftyMoveRule() =
        if wbActive then
            wbSendOutputMessage("1/2-1/2 {Draw by 50 move rule}" )
    let wbSendMove(move) =
        if wbActive then
            wbSendOutputMessage("move " + Ref.sq.[move.From] + Ref.sq.[move.To] )
    let wbSendCheckStaleMate() =
        if wbActive then
            wbSendOutputMessage("1/2-1/2 {Stalemate}" )
    let wbSendDrawByRepetition()=
        if wbActive then
            wbSendOutputMessage("1/2-1/2 {Draw by repetition}" )
    let wbSendDrawByInsufficientMaterial() =
        if wbActive then
            wbSendOutputMessage("1/2-1/2 {Draw by insufficient material}" )

    //FEN
    exception FENValidationException of string
    let FenValidate(strFEN:string) =
        let arrFen = strFEN.Split(" ".ToCharArray())
        let FenHlpMsg(iField) =
            match iField with
                |1 -> "FEN field 1: Piece placement data.\n"
                |2 -> "FEN field 2: Active color.\n"
                |3 -> "FEN field 3: Castling availability.\n"
                |4 -> "FEN field 4: En passant target square coordonates.\n"
                |5 -> "FEN field 5: Nbr of half move without capture or pawn move.\n"
                |6 -> "FEN field 6: Full move number.\n"
                |_ -> ""
        if (arrFen.Length<1 || arrFen.Length>6) then
            raise(FENValidationException("1000: A FEN string must 1 to 6 fields separated by spaces\n e.g. rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"))
        let FenCheck2Color(strFen) =
            if ((strFen <> "w") && (strFen <> "b")) then   
                raise(FENValidationException("1200: " + FenHlpMsg(2) + "The active color must be 'w' or 'b'"))
        if (arrFen.Length >= 2) then FenCheck2Color(arrFen.[1])
        let FenCheck3Castle(strFen:string) =
            let mutable iNbrDash = 0
            let mutable iNbrK = 0
            let mutable iNbrQ = 0
            let mutable iNbrk = 0
            let mutable iNbrq = 0 
            for indChar = 0 to strFen.Length-1 do
                match strFen.[indChar] with
                    |'K' -> iNbrK <- iNbrK+1
                    |'k' -> iNbrk <- iNbrk+1
                    |'Q' -> iNbrQ <- iNbrQ+1
                    |'q' -> iNbrq <- iNbrq+1
                    |'-' -> iNbrDash <- iNbrDash+1
                    |_ -> raise(FENValidationException("1300: " + FenHlpMsg(3) + "Expected character 'KQkq-'"))
            if ((iNbrK > 1) || (iNbrQ > 1) || (iNbrk > 1) || (iNbrq > 1) || (iNbrDash > 1)) then
                raise(FENValidationException("1310: " + FenHlpMsg(3) + "At least one occurrence of 'KQkq-'"))
            if ((iNbrDash = 1) && ((iNbrK = 1) || (iNbrQ = 1) || (iNbrk = 1) || (iNbrq = 1))) then
                raise(FENValidationException("1320: " + FenHlpMsg(3) + "'KQkq' or exclusive '-'"))
        if (arrFen.Length >= 3) then FenCheck3Castle(arrFen.[2])
        let FenCheck4EnPassant(strFen:string) =
            if (((strFen.[0] < 'a') || (strFen.[0] > 'h')) && (strFen.[0] <> '-')) then
                raise(FENValidationException("1400: " + FenHlpMsg(4) + "Expected character 'abcdefgh-'"))
            if (strFen.[0] = '-') then
                if (strFen.Length > 1) then
                    raise(FENValidationException("1410: " + FenHlpMsg(4) + "No expected character after '-'"))
            elif (((strFen.[0] >= 'a') && (strFen.[0] <= 'h')) && (((strFen.Length = 2) && (strFen.[1] <> '3') && (strFen.[1] <> '6')) || (strFen.Length > 2))) then
                raise(FENValidationException("1420: " + FenHlpMsg(4) + "After the pawn file, expect the rank '3' or '6'"))
        if (arrFen.Length >= 4) then FenCheck4EnPassant(arrFen.[3])
        let FenCheck5Counter50MoveDraw(strFen:string) =
            if (strFen.Length > 2) then
                raise(FENValidationException("1500: " + FenHlpMsg(5) + "1 or 2 digits for the nbr of ply for rule of 50 moves"))
            let iNbrHalfMove = ref 0
            if System.Int32.TryParse(strFen,iNbrHalfMove) then
                if ((!iNbrHalfMove < 0) || (!iNbrHalfMove > 100)) then
                    raise(FENValidationException("1520: " + FenHlpMsg(5) + "Expect a non negative half move number <= 100"))
            else
                raise(FENValidationException("1510: " + FenHlpMsg(5) + "Expect a half move number for the rule of 50 moves"))
        if (arrFen.Length >= 5) then FenCheck5Counter50MoveDraw(arrFen.[4])
        let FenCheck6NbrMove(strFen) =
            let iNbrFullMove = ref 0
            if System.Int32.TryParse(strFen,iNbrFullMove) then
                if ((!iNbrFullMove < 1) || (!iNbrFullMove > 200)) then
                    raise(FENValidationException("1610: " + FenHlpMsg(6) + "Expect a positive full move number <= 200"))
            else
                raise(FENValidationException("1600: " + FenHlpMsg(6) + "Expect a full move number"))
        if (arrFen.Length >= 6) then FenCheck6NbrMove(arrFen.[5])
    //move functions
    let MoveNameFromString(strMoveName) =
        if (strMoveName = "Standard") then Standard;
        elif (strMoveName = "CastleKingSide") then CastleKingSide
        elif (strMoveName = "CastleQueenSide") then CastleQueenSide
        elif (strMoveName = "EnPassent") then EnPassent
        elif (strMoveName = "PawnPromotion") then PawnPromotionQueen
        elif (strMoveName = "PawnPromotionQueen") then PawnPromotionQueen
        elif (strMoveName = "PawnPromotionRook") then PawnPromotionRook
        elif (strMoveName = "PawnPromotionBishop") then PawnPromotionBishop
        elif (strMoveName = "PawnPromotionKnight") then PawnPromotionKnight
        else Standard
    //game
    let IsPaused() =
        not gm.PlayerToPlay.Clock.IsTicking
    //piece
    let CreateBoard() =
        for intOrdinal = 0 to Ref.SQUARE_COUNT-1 do
            gm.Sqs.[intOrdinal] <- -1
    let EstablishHashKey() =
        gm.Hca <- 0UL
        gm.Hcb <- 0UL
        for i = 0 to Ref.SQUARE_COUNT-1 do
            let pieceid = gm.Sqs.[i] 
            if (pieceid <> -1) then
                gm.Hca <- gm.Hca ^^^ Move_do.GetHashCodeA(pieceid, i, gm.Pcs.[pieceid])
                gm.Hcb <- gm.Hcb ^^^ Move_do.GetHashCodeB(pieceid, i, gm.Pcs.[pieceid])
    let UndoMove_Internal() =
        if (gm.Mhst.Count>0) then
            let moveUndo = gm.Mhst.[gm.Mhst.Count - 1]
            gm.PlayerToPlay.Clock.Revert()
            MoveRedoList.Add(moveUndo)
            Move_do.Undo(moveUndo,gm)
            if gm.MhstStr.Count>0 then gm.MhstStr.RemoveAt(gm.MhstStr.Count - 1)
            if (gm.Mhst.Count>1) then
                let movePenultimate = gm.Mhst.[gm.Mhst.Count-2]
                gm.PlayerToPlay.Clock.TimeElapsed <- movePenultimate.TimeStamp
            else
                gm.PlayerToPlay.Clock.TimeElapsed <- new TimeSpan(0L)
            gm.PlayerToPlay.Clock.Start()
    let UndoAllMoves_Internal() =
        while (gm.Mhst.Count>0) do
            UndoMove_Internal()

    let mvPGN(mv,psn) =
        let mvpc = psn.Pcs.[mv.mvPiece]
        let part1 =
            if mv.mvName = CastleKingSide then
                "O-O"
            elif mv.mvName = CastleQueenSide then
                "O-O-O"
            else
                let strpc = if mvpc.Name <> enmName.Pawn then Piece.abbrev(mvpc.Name) else ""
                let strfrom = Ref.sq.[mv.From]
                let strcap = if (mv.pieceCaptured <> -1) then "x" else ""
                let strto = Ref.sq.[mv.To]
                //check for ambiguous
                let moves = Move_gen.GenerateLegalMovesPr psn
                let dupfrom = ref ""
                let rec testdup mvl =
                    match mvl with
                        |[] ->  ()
                        |_  ->  let move = mvl.Head
                                if Ref.sq.[move.To]=strto && Piece.abbrev(psn.Pcs.[move.mvPiece].Name)=Piece.abbrev(psn.Pcs.[mv.mvPiece].Name) && Ref.sq.[move.From]<>strfrom  then
                                    dupfrom:=Ref.sq.[move.From]
                                else testdup mvl.Tail
                testdup (ResizeArray.toList moves)
                let strfrom2 = 
                    if !dupfrom<>"" && (!dupfrom).Substring(0,1)=strfrom.Substring(0,1) then
                        strfrom.Substring(1,1)
                    elif !dupfrom<>"" then
                        strfrom.Substring(0,1)
                    elif mvpc.Name=enmName.Pawn && strcap="x" then
                        strfrom.Substring(0,1)
                    else
                        ""
                strpc+strfrom2+strcap+strto
        let part2 = match mv.mvName with
                    |PawnPromotionQueen -> "=Q"
                    |PawnPromotionRook -> "=R"
                    |PawnPromotionKnight -> "=N"
                    |PawnPromotionBishop -> "=B"
                    |_ -> ""
        let part3 =
            let col = psn.Pcs.[mv.mvPiece].pcIsWhite
            let enpr = if col then Attack.PlayerBlack else Attack.PlayerWhite
            let prchecked = Attack.is_in_check(enpr,psn,false)
            if prchecked && Move_gen.IsInCheckMate(gm) then "#"
            elif prchecked then "+"
            else ""
        part1+part2+part3


    let SetBoardPosition(istrFEN:string) =
        for i = 0 to 31 do Move_do.Capture(i,gm)
        // Break up the string into its various parts
        // rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
        let bits = istrFEN.Split(" ".ToCharArray()) 
        let pcs = bits.[0].ToCharArray()
        let col = bits.[1]
        let cas = bits.[2]
        let ep = bits.[3]
        let hm = bits.[4]
        let fm = bits.[5]
       // Match FEN pieces against actual pieces, and move them onto the board
        let SetPiecePlacement(acharPiecePlacement:char[]) =
            // Setup piece placement
            let PlacePiece(pcid,sqid,nam) =
                gm.Pcs.[pcid] <- {gm.Pcs.[pcid] with Name=nam;SquareOrd=sqid}
                gm.Sqs.[sqid] <- pcid
            let NextWP () =
                let mutable ans = -1
                for i = 8 to 15 do
                    if ans = -1 && gm.Pcs.[i].SquareOrd = -1 then ans<-i
                ans
            let NextBP () =
                let mutable ans = -1
                for i = 24 to 31 do
                    if ans = -1 && gm.Pcs.[i].SquareOrd = -1 then ans<-i
                ans
            let mutable intRank = 7
            let mutable intFile = 0
            let mutable i =0
            for intIndex=0 to acharPiecePlacement.Length-1 do
                match acharPiecePlacement.[intIndex] with
                    |'.' -> intFile<-intFile+1
                            i <- i+1
                    |'/' -> intRank<-intRank-1
                            intFile<-0
                    |'K' -> PlacePiece(4,Ref.GetOrdFR(intFile,intRank),enmName.King)
                            i <- i+1
                            intFile<-intFile+1
                    |'Q' -> if gm.Pcs.[3].SquareOrd = -1 then
                                PlacePiece(3,Ref.GetOrdFR(intFile,intRank),enmName.Queen)
                            else
                                PlacePiece(NextWP(),Ref.GetOrdFR(intFile,intRank),enmName.Queen)
                            i <- i+1
                            intFile<-intFile+1
                    |'R' -> if gm.Pcs.[0].SquareOrd = -1 then
                                PlacePiece(0,Ref.GetOrdFR(intFile,intRank),enmName.Rook)
                            elif gm.Pcs.[7].SquareOrd = -1 then
                                PlacePiece(7,Ref.GetOrdFR(intFile,intRank),enmName.Rook)
                            else
                                PlacePiece(NextWP(),Ref.GetOrdFR(intFile,intRank),enmName.Rook)
                            i <- i+1
                            intFile<-intFile+1
                    |'B' -> if gm.Pcs.[2].SquareOrd = -1 then
                                PlacePiece(2,Ref.GetOrdFR(intFile,intRank),enmName.Bishop)
                            elif gm.Pcs.[5].SquareOrd = -1 then
                                PlacePiece(5,Ref.GetOrdFR(intFile,intRank),enmName.Bishop)
                            else
                                PlacePiece(NextWP(),Ref.GetOrdFR(intFile,intRank),enmName.Bishop)
                            i <- i+1
                            intFile<-intFile+1
                    |'N' -> if gm.Pcs.[1].SquareOrd = -1 then
                                PlacePiece(1,Ref.GetOrdFR(intFile,intRank),enmName.Knight)
                            elif gm.Pcs.[6].SquareOrd = -1 then
                                PlacePiece(6,Ref.GetOrdFR(intFile,intRank),enmName.Knight)
                            else
                                PlacePiece(NextWP(),Ref.GetOrdFR(intFile,intRank),enmName.Knight)
                            i <- i+1
                            intFile<-intFile+1
                    |'P' -> let pcid=NextWP()
                            PlacePiece(pcid,Ref.GetOrdFR(intFile,intRank),enmName.Pawn)
                            let No = if intRank=1 then 0 else 1
                            gm.Pcs.[pcid] <- {gm.Pcs.[pcid] with NoOfMoves=No}
                            i <- i+1
                            intFile<-intFile+1
                    |'k' -> PlacePiece(20,Ref.GetOrdFR(intFile,intRank),enmName.King)
                            i <- i+1
                            intFile<-intFile+1
                    |'q' -> if gm.Pcs.[19].SquareOrd = -1 then
                                PlacePiece(19,Ref.GetOrdFR(intFile,intRank),enmName.Queen)
                            else
                                PlacePiece(NextBP(),Ref.GetOrdFR(intFile,intRank),enmName.Queen)
                            i <- i+1
                            intFile<-intFile+1
                    |'r' -> if gm.Pcs.[16].SquareOrd = -1 then
                                PlacePiece(16,Ref.GetOrdFR(intFile,intRank),enmName.Rook)
                            elif gm.Pcs.[23].SquareOrd = -1 then
                                PlacePiece(23,Ref.GetOrdFR(intFile,intRank),enmName.Rook)
                            else
                                PlacePiece(NextBP(),Ref.GetOrdFR(intFile,intRank),enmName.Rook)
                            i <- i+1
                            intFile<-intFile+1
                    |'b' -> if gm.Pcs.[18].SquareOrd = -1 then
                                PlacePiece(18,Ref.GetOrdFR(intFile,intRank),enmName.Bishop)
                            elif gm.Pcs.[21].SquareOrd = -1 then
                                PlacePiece(21,Ref.GetOrdFR(intFile,intRank),enmName.Bishop)
                            else
                                PlacePiece(NextBP(),Ref.GetOrdFR(intFile,intRank),enmName.Bishop)
                            i <- i+1
                            intFile<-intFile+1
                    |'n' -> if gm.Pcs.[17].SquareOrd = -1 then
                                PlacePiece(17,Ref.GetOrdFR(intFile,intRank),enmName.Knight)
                            elif gm.Pcs.[22].SquareOrd = -1 then
                                PlacePiece(22,Ref.GetOrdFR(intFile,intRank),enmName.Knight)
                            else
                                PlacePiece(NextBP(),Ref.GetOrdFR(intFile,intRank),enmName.Knight)
                            i <- i+1
                            intFile<-intFile+1
                    |'p' -> let pcid=NextBP()
                            PlacePiece(pcid,Ref.GetOrdFR(intFile,intRank),enmName.Pawn)
                            let No = if intRank=6 then 0 else 1
                            gm.Pcs.[pcid] <- {gm.Pcs.[pcid] with NoOfMoves=No}
                            i <- i+1
                            intFile<-intFile+1
                    |_ ->   if (System.Char.IsDigit(acharPiecePlacement.[intIndex])) then
                                intFile <- intFile + System.Int32.Parse(acharPiecePlacement.[intIndex].ToString())
                                i <- i + System.Int32.Parse(acharPiecePlacement.[intIndex].ToString())
                            else
                                raise(FENValidationException("Unknow character in FEN string:" + acharPiecePlacement.[intIndex].ToString()))
        SetPiecePlacement(pcs)
        // Set player to play
        gm.PlayerToPlay <- (if col = "b" then Attack.PlayerBlack else Attack.PlayerWhite)
        // White King's Rook
        let No = if cas.LastIndexOf("K")>=0 then 0 else 1
        gm.Pcs.[7] <- {gm.Pcs.[7] with NoOfMoves=No}
        gm.Pcs.[4] <- {gm.Pcs.[4] with NoOfMoves=No}
        // Black King's Rook
        let No = if cas.LastIndexOf("k")>=0 then 0 else 1
        gm.Pcs.[23] <- {gm.Pcs.[23] with NoOfMoves=No}
        gm.Pcs.[20] <- {gm.Pcs.[20] with NoOfMoves=No}
        // White Queen's Rook
        let No = if cas.LastIndexOf("Q")>=0 then 0 else 1
        gm.Pcs.[0] <- {gm.Pcs.[0] with NoOfMoves=No}
        gm.Pcs.[4] <- {gm.Pcs.[4] with NoOfMoves=No}
        // Black Queen's Rook
        let No = if cas.LastIndexOf("q")>=0 then 0 else 1
        gm.Pcs.[16] <- {gm.Pcs.[16] with NoOfMoves=No}
        gm.Pcs.[20] <- {gm.Pcs.[20] with NoOfMoves=No}
        // Half move (50 move draw) clock.
        Ref.FiftyMoveDrawBase <- System.Int32.Parse(hm)
        // Full move number. Default 1. Must be defined before En Passant.
        gm.Trn <- ((System.Int32.Parse(fm) - 1) <<< 1)
        if (not gm.PlayerToPlay.prIsWhite) then
            gm.Trn<-gm.Trn+1 // Always odd for the previous White's move 
        // En Passant
        if (ep.[0] <> '-') then
            let indFile = Ref.FileFromName(Convert.ToString(ep.[0]))
            let mutable indRank = System.Int32.Parse(Convert.ToString(ep.[1]))
            if (indRank = 6) then   // if strFen = "e6"
                indRank <- 4   // last move was e7-e5 so indRank = 6 - 2 = 4
            // else if indRank = 3, strFen = "e3" last move was e2-e4 so indRank = 3
            let piecePassed = gm.Sqs.[Ref.GetOrdFR (indFile,indRank)]
            let pc = gm.Pcs.[piecePassed]
            gm.Pcs.[piecePassed] <- {pc with NoOfMoves=1;LastMoveTurnNo=gm.Trn}
        // Recalculate the hashkey for the current position.
        gm.SetMaterialCount()
        EstablishHashKey()
    let CanMove(pr) =
        let bot,top = if pr.prIsWhite then 0,15 else 16,31
        let rec cm i =
            if i > top then
                false
            elif gm.Pcs.[i].SquareOrd <> -1 then 
                let moves = Move_gen.GenerateLegalMoves(i,gm)
                if (moves.Count > 0) then true else cm (i+1)
            else cm (i+1)
        cm bot
    let CanClaimFiftyMoveDraw() =
        if gm.Mhst.Count > 0 then gm.Mhst.[gm.Mhst.Count - 1].FiftyMoveDrawCounter >= 100 else Ref.FiftyMoveDrawBase >= 100
    let CanClaimInsufficientMaterialDraw() =
        // Return true if K vs K, K vs K+B, K vs K+N
        let rec ccd i ct =
            if ct=2 then false
            elif i=32 then true
            elif gm.Pcs.[i].Name = enmName.Pawn && gm.Pcs.[i].SquareOrd <> -1 then false
            elif gm.Pcs.[i].Name = enmName.Rook && gm.Pcs.[i].SquareOrd <> -1 then false
            elif gm.Pcs.[i].Name = enmName.Queen && gm.Pcs.[i].SquareOrd <> -1 then false
            elif gm.Pcs.[i].SquareOrd <> -1 then ccd (i+1) (ct+1)
            else ccd (i+1) ct
        ccd 0 0    
    let MakeAMove_Internal(mv) =
        MoveRedoList.Clear()
        let gmpre=gm.copy()
        let prevplayer = gmpre.PlayerToPlay
        //this swaps players
        Move_do.DoMoveOK(mv,gm)|>ignore
        let col = gm.Pcs.[mv.mvPiece].pcIsWhite
        gm.MhstStr.Add(mvPGN(mv,gmpre))
        prevplayer.Clock.Stop()
        gm.Mhst.[gm.Mhst.Count - 1] <- {gm.Mhst.[gm.Mhst.Count - 1] with TimeStamp=prevplayer.Clock.TimeElapsed}
        if (prevplayer.Intellegence = Computer) && wbActive then
            wbSendMove(mv)
            if not (CanMove(gm.PlayerToPlay)) then
                if Attack.is_in_check(gm.PlayerToPlay,gm,false)then
                    wbSendOutputMessage( if gm.PlayerToPlay.prIsWhite then "0-1 {Black mates}" else "1-0 {White mates}")
                else
                    wbSendCheckStaleMate()
            elif Move_do.CanClaimThreeMoveRepetitionDraw(gm) then
                wbSendDrawByRepetition()
            elif CanClaimFiftyMoveDraw() then
                wbSendDrawByFiftyMoveRule()
            elif CanClaimInsufficientMaterialDraw() then
                wbSendDrawByInsufficientMaterial()
        gm.PlayerToPlay.Clock.Start()
    let RedoMove_Internal() =
        if (MoveRedoList.Count>0) then
            let moveRedo = MoveRedoList.[MoveRedoList.Count-1]
            gm.PlayerToPlay.Clock.Revert()
            let gmpre=gm.copy()
            Move_do.DoMoveOK(moveRedo,gm) |> ignore
            gm.PlayerToPlay.Clock.TimeElapsed <- moveRedo.TimeStamp
            let mv = gm.Mhst.[gm.Mhst.Count - 1]
            let mv = {mv with TimeStamp=moveRedo.TimeStamp}
            gm.Mhst.[gm.Mhst.Count - 1] <- mv
            gm.MhstStr.Add(mvPGN(mv,gmpre))
            MoveRedoList.RemoveAt(MoveRedoList.Count - 1)
            gm.PlayerToPlay.Clock.Start()
    let eventTh  = new Event<_>()
    let iEventTh = eventTh.Publish
    let eventMC  = new Event<_>()
    let iEventMC = eventMC.Publish
    let eventRMM  = new Event<_>()
    let iEventRMM = eventRMM.Publish
    let New_Internal(strFEN) =
        if (strFEN = "") then Opn.load() else Opn.inOpening<-false
        let str = if (strFEN = "") then "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" else strFEN
        FenValidate(str)
        Kms.reset()
        Bms.reset()
        UndoAllMoves_Internal()
        MoveRedoList.Clear()
        SetBoardPosition(str)
        Attack.PlayerWhite.Clock <- PlayerClock.blankW
        Attack.PlayerBlack.Clock <- PlayerClock.blankB
    let NewInternal() = New_Internal("")
    let AbortThinking(pr) =
        if (pr.ThreadThought <> null && pr.ThreadThought.ThreadState = System.Threading.ThreadState.Running) then
            pr.ThreadThought.Abort()
            pr.ThreadThought.Join()
            pr.ThreadThought <- null
    let SendThinking(ans, score, thinkingTime:TimeSpan) =
(*        
        ply score time nodes pv

        Where: ply Integer giving current search depth.  
        score Integer giving current evaluation in centipawns.  
        time Current search time in centiseconds (ex: 1028 = 10.28 seconds).  
        nodes Nodes searched.  
        pv Freeform text giving current "best" line. You can continue the pv onto another line if you start each continuation line with at least four space characters.  

        Example: 

        9 156 1084 48000 Nf3 Nc6 Nc3 Nf6
        *)
        let PrincipalVariationText =
            let descl = ResizeArray.map (fun mv -> Ref.sq.[mv.From] + Ref.sq.[mv.To] + " ") ans.PrincVar
            ResizeArray.fold (+) "" descl
        let intScoreInCentipawns = score/10
        let intTimeIncentiseconds = Convert.ToInt32(thinkingTime.TotalMilliseconds/100.0)
        let an = ans.Anl
        let msg = an.SearchDepth.ToString() + " " + intScoreInCentipawns.ToString() + " " + intTimeIncentiseconds.ToString() + " " + an.PosSearched.ToString() + " " + PrincipalVariationText
        if (wbActive && ShowThinking) then
            wbSendOutputMessage(msg)
        else
            eventMC.Trigger(msg)
    let ThinkP(pr:Player) =
        //main analysis code
        System.Diagnostics.Debug.WriteLine(String.Format("Thread {0} is " + (if Funcs2.IsPondering then "pondering" else "thinking"), Thread.CurrentThread.Name))
        Search.gmanals.set(gm,Search.MaximumSearchDepth)
        let an = Search.gmanals.Anl
        let intTurnNo = gm.Trn
        pr.SetTime(gmtim.ClockFixedTimePerMove,gmtim.ClockIncrementPerMove,intTurnNo >>> 1,gmtim.ClockTime,ClockMoves)
        if (Funcs2.IsInAnalyseMode) then
            Bms.reset()
//        else
//            if (CanClaimMoveRepetitionDraw(2,gm)) then // See if' we're in a 2 move repetition position, and if so, clear the hashtable, as old hashtable entries corrupt 3MR detection
//                Bms.reset()
        if (not Funcs2.IsPondering) then
            pr.Clock.Start()
        // Here begins the main Iteractive Deepening loop of the entire search algorithm. (Cue dramatic music!)
        Search.gmanals.setimm(false) 
        let rec getBest i is =
            Search.gmanals.setSearchDepth(i)
            let(movesPV,iScore) = Search.Aspirate(pr,i,is)
            if not (Search.gmanals.getimm()) then
                Search.gmanals.PrincVar <- movesPV // The best line is then recorded
                SendThinking(Search.gmanals, iScore, DateTime.Now - pr.Clock.TurnStartTime)
                if not (not Funcs2.IsInAnalyseMode && gmtim.ClockFixedTimePerMove.TotalSeconds = 0.0 && not Funcs2.IsPondering && (DateTime.Now - pr.Clock.TurnStartTime) > pr.ThinkingTimeCutoff) &&
                    (iScore < 99999 && iScore > -99999 && i <Search.gmanals.MaxSearchDepth) then getBest (i+1) iScore // Checkmate found so dont bother searching any deeper
        getBest Search.gmanals.MinSearchDepth (Eval.Score(pr,gm,false,[]))
        System.Diagnostics.Debug.WriteLine(String.Format("Thread {0} is ending " + (if Funcs2.IsPondering then "pondering" else "thinking"), Thread.CurrentThread.Name))
        pr.ThreadThought <- null
        if (not Funcs2.IsPondering) then
            eventRMM.Trigger(Search.gmanals.PrincVar.[0])
            if EnablePondering then Funcs2.IsPondering <- true
        else
            Funcs2.IsPondering <- false
    let BlackThink() = ThinkP(Attack.PlayerBlack)
    let WhiteThink() = ThinkP(Attack.PlayerWhite)
    let eventBPC  = new Event<_>()
    let iEventBPC  = eventBPC.Publish
    let eventGP  = new Event<_>()
    let iEventGP  = eventGP.Publish
    let eventGR  = new Event<_>()
    let iEventGR  = eventGR.Publish
    //create positions for openings
    exception PgnInputException of string
    let pgnDoMove(strMove:string,psn:Posn)=
        let pcstr =
            match strMove with
                |"O-O" -> "K"
                |"O-O-O" -> "K"
                | s ->
                    match s.Substring(0,1) with
                        |"N" -> "N"
                        |"B" -> "B"
                        |"R" -> "R"
                        |"K" -> "K"
                        |"Q" -> "Q"
                        | _ -> "P"
        let movename = 
            match strMove with
            |"O-O" -> CastleKingSide
            |"O-O-O" -> CastleQueenSide
            |s when s.Contains("=Q") -> PawnPromotionQueen
            |s when s.Contains("=R") -> PawnPromotionRook
            |s when s.Contains("=B") -> PawnPromotionBishop
            |s when s.Contains("=N") -> PawnPromotionKnight
            |_ -> NullMove
        let tostr =
            let shortstr= if strMove.EndsWith("+") then strMove.Substring(0,strMove.Length-1) else strMove
            let shortstr = if shortstr.Contains("=") then shortstr.Substring(0,shortstr.Length-2) else shortstr
            shortstr.Substring(shortstr.Length-2)
        let fromstr =
            let shortstr= if strMove.EndsWith("+") then strMove.Substring(0,strMove.Length-1) else strMove
            let shortstr = if shortstr.Contains("=") then shortstr.Substring(0,shortstr.Length-2) else shortstr
            let shortstr = shortstr.Substring(0,shortstr.Length-2)
            let shortstr = if shortstr.EndsWith("x") then shortstr.Substring(0,shortstr.Length-1) else shortstr
            if shortstr.StartsWith("N")||shortstr.StartsWith("B")||shortstr.StartsWith("Q")||shortstr.StartsWith("K") then shortstr.Substring(1,shortstr.Length-1) else shortstr
        let moves = Move_gen.GenerateLegalMovesPr psn
        let rec getmove mvl =
            match mvl with
                |[] ->  raise(PgnInputException("Illegal move: " + strMove))
                |_  ->  let move = mvl.Head
                        if (movename=CastleKingSide && move.mvName=CastleKingSide)||(movename=CastleQueenSide && move.mvName=CastleQueenSide)||
                            ((fromstr="" || Ref.f.[move.From]=fromstr || Ref.r.[move.From]=fromstr) && Ref.sq.[move.To]=tostr && Piece.abbrev(psn.Pcs.[move.mvPiece].Name)=pcstr && (movename=NullMove || move.mvName=movename ) ) then
                            Move_do.DoMoveOK(move,psn)|>ignore
                            move
                        else getmove mvl.Tail
        getmove (ResizeArray.toList moves)|>ignore
        psn    
    
    let OpPsnsfromHist (mvl:string list) =
        let rec getnext (psn:Posn) ml fml =
            match ml with
                |[] -> fml
                |m ->
                    let nfml = (psn.ToFen(),m.Head)::fml
                    let npsn= pgnDoMove(m.Head,psn)
                    getnext npsn m.Tail nfml
        getnext Posn.start mvl []   
    let OpHashfromTreeView (nl : TreeNode list) =
        let rec fromTreeViewInner psn nd  = 
            match nd with
            |Opn.Nd (s,ndl) ->
                let newpsn = pgnDoMove(s,psn)
                Opn.psns.Add(newpsn.ToFen(),List.map Opn.lnFromTreeNode ndl)
                List.iter (fromTreeViewInner newpsn) ndl
            |Opn.Lf x ->
                ()
        Opn.psns.Add(Posn.start.ToFen(),List.map Opn.lnFromTreeNode nl)
        List.iter (fromTreeViewInner Posn.start) nl     
    let OpHashtoTreeView (oh : OpHash) =
        let rec ToTreeViewInner (psn:Posn) (node:TreeNode)  =
            let doelem (node:TreeNode) (psn:Posn) (m:string) =
                let newNode = new TreeNode(m)
                newNode.Tag <- psn.ToFen()
                node.Nodes.Add(newNode) |> ignore
                let npsn = pgnDoMove(m,psn.copy())
                ToTreeViewInner npsn newNode 
            let b,vrs = Opn.psns.TryGetValue(psn.ToFen())
            if b then List.iter (doelem node psn) vrs  
        let ToTreeViewBase (psn:Posn) =
            let b,vrs = Opn.psns.TryGetValue(psn.ToFen())
            let newnode str = 
                let ans = new TreeNode(str)
                ans.Tag <- psn.ToFen()
                ans
            let nodes = if b then List.map newnode vrs else [] 
            let npsns = [for m in vrs -> pgnDoMove(m,psn.copy())]
            List.iter2 ToTreeViewInner npsns nodes
            nodes
        ToTreeViewBase Posn.start
    
    let pgnMakeMove(strMove:string) =
        let pcstr =
            match strMove with
                |"O-O" -> "K"
                |"O-O-O" -> "K"
                | s ->
                    match s.Substring(0,1) with
                        |"N" -> "N"
                        |"B" -> "B"
                        |"R" -> "R"
                        |"K" -> "K"
                        |"Q" -> "Q"
                        | _ -> "P"
        let movename = 
            match strMove with
            |"O-O" -> CastleKingSide
            |"O-O-O" -> CastleQueenSide
            |s when s.Contains("=Q") -> PawnPromotionQueen
            |s when s.Contains("=R") -> PawnPromotionRook
            |s when s.Contains("=B") -> PawnPromotionBishop
            |s when s.Contains("=N") -> PawnPromotionKnight
            |_ -> NullMove
        let tostr =
            let shortstr= if strMove.EndsWith("+") then strMove.Substring(0,strMove.Length-1) else strMove
            let shortstr = if shortstr.Contains("=") then shortstr.Substring(0,shortstr.Length-2) else shortstr
            shortstr.Substring(shortstr.Length-2)
        let fromstr =
            let shortstr= if strMove.EndsWith("+") then strMove.Substring(0,strMove.Length-1) else strMove
            let shortstr = if shortstr.Contains("=") then shortstr.Substring(0,shortstr.Length-2) else shortstr
            let shortstr = shortstr.Substring(0,shortstr.Length-2)
            let shortstr = if shortstr.EndsWith("x") then shortstr.Substring(0,shortstr.Length-1) else shortstr
            if shortstr.StartsWith("N")||shortstr.StartsWith("B")||shortstr.StartsWith("Q")||shortstr.StartsWith("K") then shortstr.Substring(1,shortstr.Length-1) else shortstr
        let moves = Move_gen.GenerateLegalMovesPr gm
        let rec getmove mvl =
            match mvl with
                |[] ->  raise(PgnInputException("Illegal move: " + strMove))
                |_  ->  let move = mvl.Head
                        if (movename=CastleKingSide && move.mvName=CastleKingSide)||(movename=CastleQueenSide && move.mvName=CastleQueenSide)||
                            ((fromstr="" || Ref.f.[move.From]=fromstr || Ref.r.[move.From]=fromstr) && Ref.sq.[move.To]=tostr && Piece.abbrev(gm.Pcs.[move.mvPiece].Name)=pcstr && (movename=NullMove || move.mvName=movename ) ) then
                            MakeAMove_Internal(move)
                            eventBPC.Trigger()
                        else getmove mvl.Tail
        getmove (ResizeArray.toList moves)
    let StartThinking(pr) =
        //Put openings here
        if Opn.inOpening && not Funcs2.IsPondering then
            let opmv = Opn.getmv(gm)
            if opmv = "" then
                Opn.inOpening <- false
            else
                pgnMakeMove(opmv)
        // Bail out if unable to move
        if (not Opn.inOpening||Funcs2.IsPondering) && CanMove(pr) then
            // Send draw result if playing WinBoard
            if (wbActive && pr.Intellegence = Computer) then
                if (Move_do.CanClaimThreeMoveRepetitionDraw(gm)) then
                    wbSendDrawByRepetition()
                elif CanClaimFiftyMoveDraw() then
                    wbSendDrawByFiftyMoveRule()
                elif CanClaimInsufficientMaterialDraw() then
                    wbSendDrawByInsufficientMaterial()
            if pr.prIsWhite then
                pr.ThreadThought <- new Thread(new ThreadStart(WhiteThink))
            else
                pr.ThreadThought <- new Thread(new ThreadStart(BlackThink))
            pr.ThreadThought.Name <- (1).ToString()
            pr.ThreadThought.IsBackground <- true
            eventTh.Trigger()
            pr.ThreadThought.Priority <- System.Threading.ThreadPriority.Normal
            pr.ThreadThought.Start()
    let StartPondering(pr) =
        if not (pr.Intellegence = Computer && Attack.OtherPlayer(pr).Intellegence = Computer) && not (Funcs2.IsInAnalyseMode) && EnablePondering then
            if (PonderingHashCodeA <> gm.Hca || PonderingHashCodeB <> gm.Hcb) then
                PonderingHashCodeA <- gm.Hca
                PonderingHashCodeB <- gm.Hcb
                PonderingStart <- DateTime.Now
            if (not (Funcs2.IsThinking(pr)) && not (Funcs2.IsThinking(Attack.OtherPlayer(pr))) && Attack.OtherPlayer(pr).Intellegence = Computer && gm.PlayerToPlay = pr) then
                Funcs2.IsPondering <- true
                StartThinking(pr)
    let ResumePondering() =
        if EnablePondering && CanMove(gm.PlayerToPlay)&& not (Attack.PlayerWhite.Intellegence=Computer && Attack.PlayerBlack.Intellegence=Computer)
            && (Attack.OtherPlayer(gm.PlayerToPlay).Intellegence=Computer) && (not Funcs2.IsPondering) then
            StartPondering(gm.PlayerToPlay)
    let UndoMove() =
        UndoMove_Internal()
        eventBPC.Trigger()
    let ForceImmediateMove(pr) =
        if (Funcs2.IsThinking(pr) && not (Search.gmanals.getimm())) then
            Search.gmanals.setimm(true)
            if wbActive then
                while (pr.ThreadThought <> null) do
                    Thread.Sleep(50)
    let SuspendPondering() =
        if Funcs2.IsPondering then
            ForceImmediateMove(gm.PlayerToPlay)
        elif (Funcs2.IsThinking(gm.PlayerToPlay)) then
            ForceImmediateMove(gm.PlayerToPlay)
            UndoMove()
    let MakeNextComputerMove() =
        if (CanMove(gm.PlayerToPlay)) then
            StartThinking(gm.PlayerToPlay)
    let PausePlay() =
        gm.PlayerToPlay.Clock.Stop()
        ForceImmediateMove(gm.PlayerToPlay)
        eventGP.Trigger()
    let ResumePlay() =
        gm.PlayerToPlay.Clock.Start()
        eventGR.Trigger()
        if (gm.PlayerToPlay.Intellegence=Computer) then
            MakeNextComputerMove()
        else
            ResumePondering()
    let RedoMove() =
        SuspendPondering()
        RedoMove_Internal()
        eventBPC.Trigger()
        ResumePondering()
    let RedoAllMoves() =
        SuspendPondering()
        while (MoveRedoList.Count>0) do
            RedoMove_Internal()
        eventBPC.Trigger()
        ResumePondering()
    let UndoAllMoves() =
        SuspendPondering()
        UndoAllMoves_Internal()
        eventBPC.Trigger()
        ResumePondering()
    let Think() =
        SuspendPondering()
        MakeNextComputerMove()
    let SettingsUpdate() =
        SuspendPondering()
        ResumePondering()
    let MakeAMove(mv) =
        SuspendPondering()
        MakeAMove_Internal(mv)
        eventBPC.Trigger()
        if not (Attack.PlayerWhite.Intellegence=Computer && Attack.PlayerBlack.Intellegence=Computer) && (gm.PlayerToPlay.Intellegence=Computer)
             && (CanMove(gm.PlayerToPlay)) then
            MakeNextComputerMove()
    let New(strFEN) =
        SuspendPondering()
        New_Internal(strFEN)
        eventBPC.Trigger()
        ResumePondering()
    let StartNormalGame() =
        gm.PlayerToPlay.Clock.Start()
        ResumePondering()
    let Player_ReadyToMakeMove(mv) =
        MakeAMove_Internal(mv)
        eventBPC.Trigger()
        ResumePondering()
    let TerminateGame() =
        AbortThinking(Attack.PlayerWhite)
        AbortThinking(Attack.PlayerBlack)
    let GetBoardPosition() =
        SuspendPondering()
        let ans = gm.ToFen()
        ResumePondering()
        ans
    let GameLoadGame() =
        Kms.reset()
        Bms.reset()
        gm.SetPiecesAtStartingPositions()
        gm.PlayerToPlay <- Attack.PlayerWhite
        EstablishHashKey()
        iEventRMM.Add(fun mv -> Player_ReadyToMakeMove(mv))
        ShowThinking <- true
